using System.Web;
using System.IO;
using System.Reflection;
using System.Net.WebSockets;
using System.Text;
using System.Diagnostics;
using System.Net;
using static System.Console;
using System.Text.Json;

using ClassMap = System.Collections.Generic.Dictionary<string, (bool concurrentSafe, System.Collections.Generic.Dictionary<string, System.Reflection.MethodInfo> methodMap)>;
using MethodMap = System.ValueTuple<bool, System.Collections.Generic.Dictionary<string, System.Reflection.MethodInfo>>;

namespace SimpleServer
{
	partial class Program
	{
		static int? port = null;

		static void Main(string[] args)
		{
			Clear();

			var arguments = convert(args);
			string[] browser = null;
			var seperate = DateTime.Now.Ticks;

			foreach (var arg in arguments)
				switch (arg.Key)
				{
					case "?":
					case "h":
					case "help":
						WriteLine(@"
无参数：后台运行，自动拿一个空闲的端口；
/?或/h或/help：显示本内容；
p/或port/端口号：指定端口；
b/或browser/浏览器 相对于web目录的虚拟路径]：无工具栏方式(chrome内核)自动打开浏览器界面。若不指定虚拟路径，则显示固定的探针界面。使用此选项启动程序前必须未运行任何目标浏览器进程，否则程序立即退出，之后则不受限制；
** 若同时指定端口号和自动打开浏览器，则p必须在b之前 **
例子：
p/19931 ""b/google-chrome-stable abc/def.htm""						以19931端口为通道，打开chrome浏览器显示web/abc/def.htm页面
p/19931 ""b/google-chrome-stable abc/def.htm --start-maximized""	以19931端口为通道，打开chrome浏览器显示web/abc/def.htm页面(最大化窗口)
						".Trim());
						return;

					case "p":
					case "port":
						if (ushort.TryParse(arg.Value, out var p)) port = p;
						break;

					case "b":
					case "browser":
						var browser_param = arg.Value.Split(' ');
						var url = $"http://localhost:{port ??= autoPort()}/{(browser_param.Length < 2 ? "" : browser_param[1])}";
						browser = new[] { browser_param[0], $"{(browser_param.Length > 2 ? browser_param[2..].JoinBy(" ") : "")}", $"--user-data-dir=seperate-mode/{seperate} --app={url} {url}" };     //chrome内核游览器加上--user-data-dir参数可以启动多个实例互不影响(没有的话只能启动一个。启动第2个时直接退出)，--app参数可以以无工具栏方式启动
						break;
				}

			if (port == null) port = autoPort();
			OnStartup(arguments, browser);
			Handler.Run(port.Value, false);

			AutoResetEvent ase = new(false);

			if (browser != null)
				Task.Factory.StartNew(() =>
				{
					Process.Start(new ProcessStartInfo(browser[0], browser[1..].JoinBy(" ")) { RedirectStandardError = false }).WaitForExit();
					if(Directory.Exists($"seperate-mode/{seperate}")) Directory.Delete($"seperate-mode/{seperate}", true);
					ase.Set();
				}, TaskCreationOptions.LongRunning);

			OnRunning();
			WriteLine($"端口号：{port}");
			WriteLine("正在运行...");
			ase.WaitOne();
			OnEnd();
			Process.GetCurrentProcess().Kill();
		}

		static int autoPort()
		{
			port = 19810;
			for (port = 19810; port < 65536; port++)
				try
				{
					if (port % 10 == 0)
					{
						Clear();
						WriteLine($"正在尝试端口：{port}");
					}
					Handler.Run(port.Value, true);
					return port.Value;
				}
				catch { }

			throw new Exception("没有合适的端口");
		}

		static List<(string Key, string Value)> convert(string[] args)
		{
			List<(string, string)> res = new();

			foreach (var arg in args)
				if (arg.StartsWith("/"))
					res.Add((arg[1..], null));
				else if (!arg.Contains("/"))
					throw new Exception("不以“/”开头的参数里面必须带“/”以分隔key-value");
				else
				{
					var parts = arg.Split('/', 2);
					res.Add((parts[0], parts[1]));
				}

			return res;
		}
	}

	public partial interface ICom
	{
		/// <summary>
		/// 是否并发(多线程)安全。
		/// true：处理每次请求使用的是全局静态形式来处理，只有静态成员有效，不应出现实例成员（若调用实例成员很可能出错），节省实例化开销。
		/// false：处理每次请求都无参实例化一个新对象，所有成员都有效。
		/// </summary>
		/// <value></value>
		public static bool ConcurrentSafe { get; }
	}

	/// <summary>
	/// WebHandle标签的函数之间不能重载（可以与非WebHandle重载）
	/// </summary>
	public class WebHandle : Attribute { }

	static class Handler
	{
		static string callerDir = Environment.CurrentDirectory;

		public static void Run(int port, bool test)
		{
			HttpListener listener = new();
			listener.Prefixes.Add($"http://*:{port}/");
			listener.Start();

			if (test) listener.Abort(); else handle(listener);
		}

		async static void handle(HttpListener listener)
		{
			var context = await listener.GetContextAsync();
			var (request, response) = (context.Request, context.Response);

			if (request.IsWebSocketRequest)
				ws(await context.AcceptWebSocketAsync(null));
			else
				http(request, response);

			handle(listener);
		}

		async static void http(HttpListenerRequest request, HttpListenerResponse response)
		{
			try
			{
				var file = HttpUtility.UrlDecode(request.Url?.LocalPath);
				if (request.HttpMethod.ToLower() == "post")
				{
					if (file != "/")
					{
						file = callerDir + "/upload" + file;
						FileInfo fi = new(file);
						if (!Directory.Exists(fi.DirectoryName)) Directory.CreateDirectory(fi.DirectoryName);

						using (FileStream fs = new(file, FileMode.Create))
						{
							await request.InputStream.CopyToAsync(fs);
							fs.Close();
						}
					}
					else
						try
						{
							response.Headers["Access-Control-Allow-Origin"] = "*";
							Dictionary<string, object> parameters = null;
							try
							{
								using (StreamReader reader = new(request.InputStream))
									parameters = JsonSerializer.Deserialize<Dictionary<string, object>>(await reader.ReadToEndAsync());
							}
							catch { }

							var result = execute(request.QueryString["cmd"], parameters);
							await response.Reply(result, true);
						}
						catch (Exception ex)
						{
							try { await response.Reply(new { ok = false, msg = ex.Message, stack = ex.StackTrace }, false); } catch { }
							WriteLine(ex.Message);
							WriteLine(ex.StackTrace);
						}
				}
				else if (file == "/")
					await response.Reply(Encoding.UTF8.GetBytes($"<h1>SimpleServer probe</h1><p>ver : {ICom.BuiltWhen}</p><p>dir : {callerDir}</p>"), true);
				else
				{
					file = callerDir + "/web" + file;
					if (File.Exists(file))
					{
						FileInfo fi = new(file);
						var ext = fi.Extension.ToLower();

						var target = fi.ResolveLinkTarget(true)?.FullName;
						if (target != null) fi = new(target);

						response.ContentType = null;
						if (File.Exists("MIME"))
						{
							var line = File.ReadAllLines("MIME").SingleOrDefault(line => line.StartsWith(ext + ":"));
							if (line != null) response.ContentType = line[(ext.Length + 1)..];
						}

						if (response.ContentType == null)
						{
							if (MIME.ContainsKey(ext))
								response.ContentType = MIME[ext];
							else if (fi.Length < 100000)
								response.ContentType = "text/plain";
							else
								response.ContentType = "application/octet-stream";
						}

						var length = response.ContentLength64 = fi.Length;
						response.Headers[HttpResponseHeader.ContentRange] = $"bytes 0-{length - 1}/{length}";
						response.Headers[HttpResponseHeader.AcceptRanges] = "bytes";

						response.Headers["Access-Control-Allow-Origin"] = "*";

						using (FileStream fs = new(file, FileMode.Open))
						{
							await fs.CopyToAsync(response.OutputStream);
							fs.Close();
						}
					}
					else
						response.StatusCode = 404;
				}
			}
			catch { }
			response.Close();
		}

		async static Task Reply(this HttpListenerResponse response, object result, bool ok)
		{
			byte[] res;

			if (result is IEnumerable<byte>)
				res = (result as IEnumerable<byte>).ToArray();
			else
				res = Encoding.Default.GetBytes(JsonSerializer.Serialize(ok ? new { ok = true, data = result } : result));

			response.ContentLength64 = res.Length;
			response.Headers[HttpResponseHeader.ContentRange] = $"bytes 0-{res.Length - 1}/{res.Length}";
			response.Headers[HttpResponseHeader.AcceptRanges] = "bytes";
			await response.OutputStream.WriteAsync(res);
		}

		async static void ws(HttpListenerWebSocketContext socket)
		{
			var bs = new byte[100000];
			string content;

			try
			{
				var received = await socket.WebSocket.ReceiveAsync(bs, CancellationToken.None);
				content = Encoding.Default.GetString(bs, 0, received.Count);
			}
			catch { return; }

			try
			{
				var dict = JsonSerializer.Deserialize<Dictionary<string, object>>(content);
				if (dict == null) throw new Exception("请求参数格式出错");

				var param = dict["param"].ToString();
				Dictionary<string, object> parameters = null;
				try { parameters = JsonSerializer.Deserialize<Dictionary<string, object>>(param); } catch { }

				var result = execute(dict["cmd"].ToString(), parameters);
				await socket.Send(result, true);
			}
			catch (Exception ex)
			{
				try { await socket.Send(new { ok = false, msg = ex.Message, stack = ex.StackTrace }, false); } catch { }
				WriteLine(ex.Message);
				WriteLine(ex.StackTrace);
			}

			ws(socket);
		}

		async static Task Send(this HttpListenerWebSocketContext socket, object result, bool ok)
		{
			if (result is IEnumerable<byte>)
				await socket.WebSocket.SendAsync((result as IEnumerable<byte>).ToArray(), WebSocketMessageType.Binary, true, CancellationToken.None);
			else
				await socket.WebSocket.SendAsync(Encoding.Default.GetBytes(JsonSerializer.Serialize(ok ? new { ok = true, data = result } : result)), WebSocketMessageType.Text, true, CancellationToken.None);
		}

		static Assembly _asm = null;
		static Assembly asm = _asm ??= Assembly.GetEntryAssembly();
		static ClassMap _classes = null;
		static ClassMap classes => _classes ??= asm.GetTypes().Where(t => t.GetInterface("ICom") != null).ToDictionary(t => t.FullName, methodMap);
		static MethodMap methodMap(Type t)
		{
			var methods = t.GetMethods().Where(m => m.GetCustomAttribute<WebHandle>() != null);
			Dictionary<string, MethodInfo> result = new();
			foreach (var m in methods)
				if (result.ContainsKey(m.Name))
					throw new Exception($"WebHandle标签的函数之间不能重载，而{t.FullName}.{m.Name}被另一个WebHandle函数重载");
				else
					result[m.Name] = m;

			return (Convert.ToBoolean(t.GetProperty("ConcurrentSafe", BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy).GetValue(null)), result);
		}

		static object execute(string callee, Dictionary<string, object> parameters)
		{
			var cmd = callee.Split('.');
			var (cls, method) = (cmd.Take(cmd.Length - 1).JoinBy("."), cmd.Last());
			if (!classes.ContainsKey(cls)) throw new Exception($"未定义{cls}类");

			var clas = classes[cls];
			if (!clas.methodMap.ContainsKey(method)) throw new Exception($"未定义{callee}函数");

			List<object> args = new();
			foreach (var arg in clas.methodMap[method].GetParameters().OrderBy(a => a.Position))
			{
				if (parameters == null || !parameters.ContainsKey(arg.Name))
				{
					if (arg.HasDefaultValue) args.Add(arg.DefaultValue); else throw new Exception($"调用{callee}时缺少必要参数{arg.Name}");
					continue;
				}

				var val = parameters[arg.Name].ToString();
				switch (arg.ParameterType.Name)
				{
					case "Byte":
						byte.TryParse(val, out byte bt);
						args.Add(bt);
						break;

					case "Int32":
						int.TryParse(val, out int it);
						args.Add(it);
						break;

					case "Int64":
						long.TryParse(val, out long lg);
						args.Add(lg);
						break;

					case "Decimal":
						decimal.TryParse(val, out decimal dc);
						args.Add(dc);
						break;

					case "Boolean":
						bool.TryParse(val, out bool bl);
						args.Add(bl);
						break;

					case "DateTime":
						DateTime.TryParse(val, out DateTime dt);
						args.Add(dt);
						break;

					case "String":
						args.Add(val);
						break;
				}
			}

			try
			{
				return clas.methodMap[method].Invoke(clas.concurrentSafe ? null : asm.CreateInstance(cls), args.ToArray());
			}
			catch (Exception ex)
			{
				throw ex.InnerException ?? ex;
			}
		}

		internal static string JoinBy(this IEnumerable<object> parts, string glue) => string.Join(glue, parts);

		static Dictionary<string, string> MIME = new()
		{
			{ ".323", "text/h323" },
			{ ".aaf", "application/octet-stream" },
			{ ".aca", "application/octet-stream" },
			{ ".accdb", "application/msaccess" },
			{ ".accde", "application/msaccess" },
			{ ".accdt", "application/msaccess" },
			{ ".acx", "application/internet-property-stream" },
			{ ".afm", "application/octet-stream" },
			{ ".ai", "application/postscript" },
			{ ".aif", "audio/x-aiff" },
			{ ".aifc", "audio/aiff" },
			{ ".aiff", "audio/aiff" },
			{ ".application", "application/x-ms-application" },
			{ ".art", "image/x-jg" },
			{ ".asd", "application/octet-stream" },
			{ ".asf", "video/x-ms-asf" },
			{ ".asi", "application/octet-stream" },
			{ ".asm", "text/plain" },
			{ ".asr", "video/x-ms-asf" },
			{ ".asx", "video/x-ms-asf" },
			{ ".atom", "application/atom+xml" },
			{ ".au", "audio/basic" },
			{ ".avi", "video/x-msvideo" },
			{ ".axs", "application/olescript" },
			{ ".bas", "text/plain" },
			{ ".bcpio", "application/x-bcpio" },
			{ ".bin", "application/octet-stream" },
			{ ".bmp", "image/bmp" },
			{ ".c", "text/plain" },
			{ ".cab", "application/octet-stream" },
			{ ".calx", "application/vnd.ms-office.calx" },
			{ ".cat", "application/vnd.ms-pki.seccat" },
			{ ".cdf", "application/x-cdf" },
			{ ".chm", "application/octet-stream" },
			{ ".class", "application/x-java-applet" },
			{ ".clp", "application/x-msclip" },
			{ ".cmx", "image/x-cmx" },
			{ ".cnf", "text/plain" },
			{ ".cod", "image/cis-cod" },
			{ ".cpio", "application/x-cpio" },
			{ ".cpp", "text/plain" },
			{ ".crd", "application/x-mscardfile" },
			{ ".crl", "application/pkix-crl" },
			{ ".crt", "application/x-x509-ca-cert" },
			{ ".csh", "application/x-csh" },
			{ ".css", "text/css" },
			{ ".csv", "application/octet-stream" },
			{ ".cur", "application/octet-stream" },
			{ ".dcr", "application/x-director" },
			{ ".deploy", "application/octet-stream" },
			{ ".der", "application/x-x509-ca-cert" },
			{ ".dib", "image/bmp" },
			{ ".dir", "application/x-director" },
			{ ".disco", "text/xml" },
			{ ".dll", "application/x-msdownload" },
			{ ".dll.config", "text/xml" },
			{ ".dlm", "text/dlm" },
			{ ".doc", "application/msword" },
			{ ".docm", "application/vnd.ms-word.document.macroEnabled.12" },
			{ ".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" },
			{ ".dot", "application/msword" },
			{ ".dotm", "application/vnd.ms-word.template.macroEnabled.12" },
			{ ".dotx", "application/vnd.openxmlformats-officedocument.wordprocessingml.template" },
			{ ".dsp", "application/octet-stream" },
			{ ".dtd", "text/xml" },
			{ ".dvi", "application/x-dvi" },
			{ ".dwf", "drawing/x-dwf" },
			{ ".dwp", "application/octet-stream" },
			{ ".dxr", "application/x-director" },
			{ ".eml", "message/rfc822" },
			{ ".emz", "application/octet-stream" },
			{ ".eot", "application/octet-stream" },
			{ ".eps", "application/postscript" },
			{ ".etx", "text/x-setext" },
			{ ".evy", "application/envoy" },
			{ ".exe", "application/octet-stream" },
			{ ".exe.config", "text/xml" },
			{ ".fdf", "application/vnd.fdf" },
			{ ".fif", "application/fractals" },
			{ ".fla", "application/octet-stream" },
			{ ".flr", "x-world/x-vrml" },
			{ ".flv", "video/x-flv" },
			{ ".gif", "image/gif" },
			{ ".gtar", "application/x-gtar" },
			{ ".gz", "application/x-gzip" },
			{ ".h", "text/plain" },
			{ ".hdf", "application/x-hdf" },
			{ ".hdml", "text/x-hdml" },
			{ ".hhc", "application/x-oleobject" },
			{ ".hhk", "application/octet-stream" },
			{ ".hhp", "application/octet-stream" },
			{ ".hlp", "application/winhlp" },
			{ ".hqx", "application/mac-binhex40" },
			{ ".hta", "application/hta" },
			{ ".htc", "text/x-component" },
			{ ".htm", "text/html" },
			{ ".html", "text/html" },
			{ ".htt", "text/webviewhtml" },
			{ ".hxt", "text/html" },
			{ ".ico", "image/x-icon" },
			{ ".ics", "application/octet-stream" },
			{ ".ief", "image/ief" },
			{ ".iii", "application/x-iphone" },
			{ ".inf", "application/octet-stream" },
			{ ".ins", "application/x-internet-signup" },
			{ ".isp", "application/x-internet-signup" },
			{ ".IVF", "video/x-ivf" },
			{ ".jar", "application/java-archive" },
			{ ".java", "application/octet-stream" },
			{ ".jck", "application/liquidmotion" },
			{ ".jcz", "application/liquidmotion" },
			{ ".jfif", "image/pjpeg" },
			{ ".jpb", "application/octet-stream" },
			{ ".jpe", "image/jpeg" },
			{ ".jpeg", "image/jpeg" },
			{ ".jpg", "image/jpeg" },
			{ ".js", "application/x-javascript" },
			{ ".jsx", "text/jscript" },
			{ ".latex", "application/x-latex" },
			{ ".lit", "application/x-ms-reader" },
			{ ".lpk", "application/octet-stream" },
			{ ".lsf", "video/x-la-asf" },
			{ ".lsx", "video/x-la-asf" },
			{ ".lzh", "application/octet-stream" },
			{ ".m13", "application/x-msmediaview" },
			{ ".m14", "application/x-msmediaview" },
			{ ".m1v", "video/mpeg" },
			{ ".m3u", "audio/x-mpegurl" },
			{ ".man", "application/x-troff-man" },
			{ ".manifest", "application/x-ms-manifest" },
			{ ".map", "text/plain" },
			{ ".mdb", "application/x-msaccess" },
			{ ".mdp", "application/octet-stream" },
			{ ".me", "application/x-troff-me" },
			{ ".mht", "message/rfc822" },
			{ ".mhtml", "message/rfc822" },
			{ ".mid", "audio/mid" },
			{ ".midi", "audio/mid" },
			{ ".mix", "application/octet-stream" },
			{ ".mmf", "application/x-smaf" },
			{ ".mno", "text/xml" },
			{ ".mny", "application/x-msmoney" },
			{ ".mov", "video/quicktime" },
			{ ".movie", "video/x-sgi-movie" },
			{ ".mp2", "video/mpeg" },
			{ ".mp3", "audio/mpeg" },
			{ ".mp4", "video/mp4" },
			{ ".mpa", "video/mpeg" },
			{ ".mpe", "video/mpeg" },
			{ ".mpeg", "video/mpeg" },
			{ ".mpg", "video/mpeg" },
			{ ".mpp", "application/vnd.ms-project" },
			{ ".mpv2", "video/mpeg" },
			{ ".ms", "application/x-troff-ms" },
			{ ".msi", "application/octet-stream" },
			{ ".mso", "application/octet-stream" },
			{ ".mvb", "application/x-msmediaview" },
			{ ".mvc", "application/x-miva-compiled" },
			{ ".nc", "application/x-netcdf" },
			{ ".nsc", "video/x-ms-asf" },
			{ ".nws", "message/rfc822" },
			{ ".ocx", "application/octet-stream" },
			{ ".oda", "application/oda" },
			{ ".odc", "text/x-ms-odc" },
			{ ".ods", "application/oleobject" },
			{ ".one", "application/onenote" },
			{ ".onea", "application/onenote" },
			{ ".onetoc", "application/onenote" },
			{ ".onetoc2", "application/onenote" },
			{ ".onetmp", "application/onenote" },
			{ ".onepkg", "application/onenote" },
			{ ".osdx", "application/opensearchdescription+xml" },
			{ ".p10", "application/pkcs10" },
			{ ".p12", "application/x-pkcs12" },
			{ ".p7b", "application/x-pkcs7-certificates" },
			{ ".p7c", "application/pkcs7-mime" },
			{ ".p7m", "application/pkcs7-mime" },
			{ ".p7r", "application/x-pkcs7-certreqresp" },
			{ ".p7s", "application/pkcs7-signature" },
			{ ".pbm", "image/x-portable-bitmap" },
			{ ".pcx", "application/octet-stream" },
			{ ".pcz", "application/octet-stream" },
			{ ".pdf", "application/pdf" },
			{ ".pfb", "application/octet-stream" },
			{ ".pfm", "application/octet-stream" },
			{ ".pfx", "application/x-pkcs12" },
			{ ".pgm", "image/x-portable-graymap" },
			{ ".pko", "application/vnd.ms-pki.pko" },
			{ ".pma", "application/x-perfmon" },
			{ ".pmc", "application/x-perfmon" },
			{ ".pml", "application/x-perfmon" },
			{ ".pmr", "application/x-perfmon" },
			{ ".pmw", "application/x-perfmon" },
			{ ".png", "image/png" },
			{ ".pnm", "image/x-portable-anymap" },
			{ ".pnz", "image/png" },
			{ ".pot", "application/vnd.ms-powerpoint" },
			{ ".potm", "application/vnd.ms-powerpoint.template.macroEnabled.12" },
			{ ".potx", "application/vnd.openxmlformats-officedocument.presentationml.template" },
			{ ".ppam", "application/vnd.ms-powerpoint.addin.macroEnabled.12" },
			{ ".ppm", "image/x-portable-pixmap" },
			{ ".pps", "application/vnd.ms-powerpoint" },
			{ ".ppsm", "application/vnd.ms-powerpoint.slideshow.macroEnabled.12" },
			{ ".ppsx", "application/vnd.openxmlformats-officedocument.presentationml.slideshow" },
			{ ".ppt", "application/vnd.ms-powerpoint" },
			{ ".pptm", "application/vnd.ms-powerpoint.presentation.macroEnabled.12" },
			{ ".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation" },
			{ ".prf", "application/pics-rules" },
			{ ".prm", "application/octet-stream" },
			{ ".prx", "application/octet-stream" },
			{ ".ps", "application/postscript" },
			{ ".psd", "application/octet-stream" },
			{ ".psm", "application/octet-stream" },
			{ ".psp", "application/octet-stream" },
			{ ".pub", "application/x-mspublisher" },
			{ ".qt", "video/quicktime" },
			{ ".qtl", "application/x-quicktimeplayer" },
			{ ".qxd", "application/octet-stream" },
			{ ".ra", "audio/x-pn-realaudio" },
			{ ".ram", "audio/x-pn-realaudio" },
			{ ".rar", "application/octet-stream" },
			{ ".ras", "image/x-cmu-raster" },
			{ ".rf", "image/vnd.rn-realflash" },
			{ ".rgb", "image/x-rgb" },
			{ ".rm", "application/vnd.rn-realmedia" },
			{ ".rmi", "audio/mid" },
			{ ".roff", "application/x-troff" },
			{ ".rpm", "audio/x-pn-realaudio-plugin" },
			{ ".rtf", "application/rtf" },
			{ ".rtx", "text/richtext" },
			{ ".scd", "application/x-msschedule" },
			{ ".sct", "text/scriptlet" },
			{ ".sea", "application/octet-stream" },
			{ ".setpay", "application/set-payment-initiation" },
			{ ".setreg", "application/set-registration-initiation" },
			{ ".sgml", "text/sgml" },
			{ ".sh", "application/x-sh" },
			{ ".shar", "application/x-shar" },
			{ ".sit", "application/x-stuffit" },
			{ ".sldm", "application/vnd.ms-powerpoint.slide.macroEnabled.12" },
			{ ".sldx", "application/vnd.openxmlformats-officedocument.presentationml.slide" },
			{ ".smd", "audio/x-smd" },
			{ ".smi", "application/octet-stream" },
			{ ".smx", "audio/x-smd" },
			{ ".smz", "audio/x-smd" },
			{ ".snd", "audio/basic" },
			{ ".snp", "application/octet-stream" },
			{ ".spc", "application/x-pkcs7-certificates" },
			{ ".spl", "application/futuresplash" },
			{ ".src", "application/x-wais-source" },
			{ ".ssm", "application/streamingmedia" },
			{ ".sst", "application/vnd.ms-pki.certstore" },
			{ ".stl", "application/vnd.ms-pki.stl" },
			{ ".sv4cpio", "application/x-sv4cpio" },
			{ ".sv4crc", "application/x-sv4crc" },
			{ ".swf", "application/x-shockwave-flash" },
			{ ".t", "application/x-troff" },
			{ ".tar", "application/x-tar" },
			{ ".tcl", "application/x-tcl" },
			{ ".tex", "application/x-tex" },
			{ ".texi", "application/x-texinfo" },
			{ ".texinfo", "application/x-texinfo" },
			{ ".tgz", "application/x-compressed" },
			{ ".thmx", "application/vnd.ms-officetheme" },
			{ ".thn", "application/octet-stream" },
			{ ".tif", "image/tiff" },
			{ ".tiff", "image/tiff" },
			{ ".toc", "application/octet-stream" },
			{ ".tr", "application/x-troff" },
			{ ".trm", "application/x-msterminal" },
			{ ".tsv", "text/tab-separated-values" },
			{ ".ttf", "application/octet-stream" },
			{ ".txt", "text/plain" },
			{ ".u32", "application/octet-stream" },
			{ ".uls", "text/iuls" },
			{ ".ustar", "application/x-ustar" },
			{ ".vbs", "text/vbscript" },
			{ ".vcf", "text/x-vcard" },
			{ ".vcs", "text/plain" },
			{ ".vdx", "application/vnd.ms-visio.viewer" },
			{ ".vml", "text/xml" },
			{ ".vsd", "application/vnd.visio" },
			{ ".vss", "application/vnd.visio" },
			{ ".vst", "application/vnd.visio" },
			{ ".vsto", "application/x-ms-vsto" },
			{ ".vsw", "application/vnd.visio" },
			{ ".vsx", "application/vnd.visio" },
			{ ".vtx", "application/vnd.visio" },
			{ ".wav", "audio/wav" },
			{ ".wax", "audio/x-ms-wax" },
			{ ".wbmp", "image/vnd.wap.wbmp" },
			{ ".wcm", "application/vnd.ms-works" },
			{ ".wdb", "application/vnd.ms-works" },
			{ ".wks", "application/vnd.ms-works" },
			{ ".wm", "video/x-ms-wm" },
			{ ".wma", "audio/x-ms-wma" },
			{ ".wmd", "application/x-ms-wmd" },
			{ ".wmf", "application/x-msmetafile" },
			{ ".wml", "text/vnd.wap.wml" },
			{ ".wmlc", "application/vnd.wap.wmlc" },
			{ ".wmls", "text/vnd.wap.wmlscript" },
			{ ".wmlsc", "application/vnd.wap.wmlscriptc" },
			{ ".wmp", "video/x-ms-wmp" },
			{ ".wmv", "video/x-ms-wmv" },
			{ ".wmx", "video/x-ms-wmx" },
			{ ".wmz", "application/x-ms-wmz" },
			{ ".wps", "application/vnd.ms-works" },
			{ ".wri", "application/x-mswrite" },
			{ ".wrl", "x-world/x-vrml" },
			{ ".wrz", "x-world/x-vrml" },
			{ ".wsdl", "text/xml" },
			{ ".wvx", "video/x-ms-wvx" },
			{ ".x", "application/directx" },
			{ ".xaf", "x-world/x-vrml" },
			{ ".xaml", "application/xaml+xml" },
			{ ".xap", "application/x-silverlight-app" },
			{ ".xbap", "application/x-ms-xbap" },
			{ ".xbm", "image/x-xbitmap" },
			{ ".xdr", "text/plain" },
			{ ".xla", "application/vnd.ms-excel" },
			{ ".xlam", "application/vnd.ms-excel.addin.macroEnabled.12" },
			{ ".xlc", "application/vnd.ms-excel" },
			{ ".xlm", "application/vnd.ms-excel" },
			{ ".xls", "application/vnd.ms-excel" },
			{ ".xlsb", "application/vnd.ms-excel.sheet.binary.macroEnabled.12" },
			{ ".xlsm", "application/vnd.ms-excel.sheet.macroEnabled.12" },
			{ ".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" },
			{ ".xlt", "application/vnd.ms-excel" },
			{ ".xltm", "application/vnd.ms-excel.template.macroEnabled.12" },
			{ ".xltx", "application/vnd.openxmlformats-officedocument.spreadsheetml.template" },
			{ ".xlw", "application/vnd.ms-excel" },
			{ ".xml", "text/xml" },
			{ ".xof", "x-world/x-vrml" },
			{ ".xpm", "image/x-xpixmap" },
			{ ".xps", "application/vnd.ms-xpsdocument" },
			{ ".xsd", "text/xml" },
			{ ".xsf", "text/xml" },
			{ ".xsl", "text/xml" },
			{ ".xslt", "text/xml" },
			{ ".xsn", "application/octet-stream" },
			{ ".xtp", "application/octet-stream" },
			{ ".xwd", "image/x-xwindowdump" },
			{ ".z", "application/x-compress" },
			{ ".zip", "application/x-zip-compressed" },
		};
	}
}
